package com.devops.admin.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import com.devops.admin.model.DOSKubernetesResource;
import com.devops.admin.po.*;
import com.devops.admin.service.*;
import com.devops.admin.util.Assert;
import com.devops.admin.util.K8sClientUtil;
import com.devops.admin.util.TimeUtil;
import com.devops.admin.util.YamlUtils;
import com.devops.admin.vo.DOSNamespaceChart;
import com.devops.admin.vo.DOSPodVo;
import com.devops.admin.vo.MonitorPodStatusVo;
import com.devops.constant.DevOpsConstant;
import com.devops.constant.VolumeTypeEnum;
import com.devops.shiro.vm.BaseUserVM;
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.PodResource;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @ClassName DOSK8sPodService
 * @Description TODO
 * @Author hepingguo
 * @Date 2022/5/20 08:48
 * @Version 1.0
 **/
@Service
@Slf4j
public class DOSK8sPodServiceImpl implements DOSK8sPodService {

    @Autowired
    private DOSKubernetesResourceService dosKubernetesResourceService;
    @Autowired
    DOSReleaseOutService dosReleaseOutService;

    @Override
    public PodList dealPodList(DOSNamespaceChart namespaceChart) {
        if (namespaceChart.getClient() == null) {
            KubernetesClient client = K8sClientUtil.getKubernetesClient(namespaceChart.getConfigPath(),namespaceChart.getConfigContent());
            namespaceChart.setClient(client);
        }

        NonNamespaceOperation<Pod, PodList, PodResource> operation = namespaceChart.getClient().pods().inNamespace(namespaceChart.getNamespace());
        PodList podList;
        if (namespaceChart.getReleaseType() != null && namespaceChart.getReleaseType().intValue() == DevOpsConstant.ReleaseType.YAML.intValue()){
            // 使用yaml文件方式部署的查询方式
            // 查询labels
            DOSKubernetesResource kubernetesResource = dosKubernetesResourceService.getById(namespaceChart.getReleaseId());
            // 设置过滤向量
            LabelSelector selector = new LabelSelector();
            if (StrUtil.isNotBlank(kubernetesResource.getYamlValue())) {
                Map<String, Object> resultMatchLabels = getMatchLabels(kubernetesResource.getYamlValue());
                if (CollectionUtil.isNotEmpty(resultMatchLabels)) {
                    Map<String,String> matchLabels = new LinkedHashMap<>();
                    resultMatchLabels.forEach((k,v)-> matchLabels.put(k,v.toString()));
                    selector.setMatchLabels(matchLabels);
                }
            }
            podList = operation.withLabelSelector(selector).list();
        } else {
            // 使用helm方式部署的查询方式
            podList = operation.withLabel(DevOpsConstant.K8sConfig.POD_LABEL_RELEASE_NAME, namespaceChart.getReleaseName())
                    .withLabel(DevOpsConstant.K8sConfig.POD_LABEL_CHART_NAME, namespaceChart.getChartName())
                    .list();
        }
        return podList;
    }

    @Override
    public List<DOSPod> getK8sClusterPod(DOSNamespaceChart namespaceChart) {
        PodList podList = dealPodList(namespaceChart);

        // 组装查询结果
        List<DOSPod> dosPodList = new ArrayList<>();
        if (podList == null){
            return dosPodList;
        }

        for (Pod pod : podList.getItems()) {
            String podName = pod.getMetadata().getName();
            String dosPodName;
            String dosPodReady;
            String dosPodStatus;
            Integer dosPodRestarts;
            String dosPodAge;
            PodStatus podStatus = pod.getStatus();
            List<ContainerStatus> containerStatus = podStatus.getContainerStatuses();
            if (containerStatus.size() > 0) {
                ContainerStatus firstContainerStatus = containerStatus.get(0);
                if (firstContainerStatus.getReady()) {
                    dosPodReady = "1/1";
                } else {
                    dosPodReady = "0/1";
                }
                ContainerState containerState = firstContainerStatus.getState();
                if (containerState.getRunning() != null ) {
                    dosPodStatus = DevOpsConstant.PodConstant.RUNNING;
                } else if (containerState.getTerminated() != null) {
                    dosPodStatus = DevOpsConstant.PodConstant.TERMINATING;
                } else if (containerState.getWaiting() != null) {
                    dosPodStatus = containerState.getWaiting().getReason();
                } else {
                    dosPodStatus = DevOpsConstant.PodConstant.UNKNOWN;
                }
                dosPodName = podName;
                dosPodRestarts = firstContainerStatus.getRestartCount();
                dosPodAge = TimeUtil.getDate(podStatus.getStartTime());
                DOSPod dosPod = DOSPod.builder().name(dosPodName).ready(dosPodReady).status(dosPodStatus).restarts(dosPodRestarts).age(dosPodAge).build();
                dosPodList.add(dosPod);
            }
        }

        return dosPodList.stream().sorted(Comparator.comparing(DOSPod::getAge).reversed()).collect(Collectors.toList());
    }

    @Override
    public DOSPodInfoPo queryPodInfo(int releaseId, String podName,Integer type) {
        Assert.isTrueMvc(releaseId>0,"releaseId不能为空");
        Assert.isTrueMvc(StrUtil.isNotBlank(podName),"podName不能为空");
        DOSNamespaceChart namespaceChart;
        if (type == 1) {
            namespaceChart = dosReleaseOutService.getInfoByReleaseId(releaseId);
        }else{
            namespaceChart = dosReleaseOutService.getInfoByResourceId(releaseId);
        }
        Assert.isTrueMvc(null!=namespaceChart,"请检查releaseId是否正确");
        KubernetesClient client = K8sClientUtil.getKubernetesClient(namespaceChart.getConfigPath(),namespaceChart.getConfigContent());
        Pod pod = client.pods().inNamespace(namespaceChart.getNamespace()).withName(podName).get();
        DOSPodInfoPo dosPodInfoPo = null;
        if (pod != null) {
            PodSpec podSpec = pod.getSpec();
            PodStatus podStatus = pod.getStatus();
            ObjectMeta podMeta = pod.getMetadata();
            //封装 containers
            List<DOSPodContainerPo> containers = new ArrayList<>();
            Map<String, String> volumeMap = setContainers(podSpec, podStatus, containers);

            //封装 volumes;
            List<DOSPodVolumePo> volumes = new ArrayList<>();
            setVolumes(podSpec, volumes, volumeMap);

            String created = TimeUtil.getDate(podMeta.getCreationTimestamp());
            String name = podMeta.getName();
            String namespace = podMeta.getNamespace();
            String[] labels = getSplitStr(podMeta.getLabels());
            String[] annotations = getSplitStr(podMeta.getAnnotations());

            String controlledBy = "--";
            String status = CollectionUtil.isNotEmpty(containers) ? containers.get(0).getStatus() : "--";
            String node = podSpec.getNodeName();
            String podIP = podStatus.getPodIP();
            StringBuilder podIPsSb = new StringBuilder();
            for (PodIP podIPSingle : podStatus.getPodIPs()) {
                podIPsSb.append(podIPSingle.getIp());
                podIPsSb.append("  ");
            }
            String priorityClass = StrUtil.isNotBlank(podSpec.getPriorityClassName()) ? podSpec.getPriorityClassName() : "--";
            String qoSClass = StrUtil.isNotBlank(podStatus.getQosClass()) ? podStatus.getQosClass() : "--";
            List<PodCondition> conditions = podStatus.getConditions();
            //将时间转换为本地时间
            conditions.forEach(con->{
                if (StrUtil.isNotBlank(con.getLastProbeTime())){
                    con.setLastProbeTime(TimeUtil.getDate(con.getLastProbeTime()));
                }
                if (StrUtil.isNotBlank(con.getLastTransitionTime())){
                    con.setLastTransitionTime(TimeUtil.getDate(con.getLastTransitionTime()));
                }
            });
            List<Toleration> tolerations = podSpec.getTolerations();


            //封装Events
            EventList eventList = client.v1().events().inNamespace(namespaceChart.getNamespace()).list();
            List<DOSEventPo> events = new ArrayList<>();
            setEvents(eventList, events, podName);

            dosPodInfoPo = DOSPodInfoPo.builder().created(created).name(name)
                    .namespace(namespace).labels(labels).annotations(annotations).controlledBy(controlledBy)
                    .status(status).node(node).podIP(podIP).podIPs(podIPsSb.toString().trim()).priorityClass(priorityClass)
                    .qoSClass(qoSClass).conditions(conditions).tolerations(tolerations).containers(containers)
                    .volumes(volumes).events(events).build();
        }
        return dosPodInfoPo;
    }

    private void setEvents(EventList eventList, List<DOSEventPo> events, String podName) {
        if (eventList != null ) {
            eventList.getItems().stream().filter(event -> event.getInvolvedObject().getName().equals(podName)).forEach(event -> {
                String message = event.getMessage();
                String source = event.getSource().getComponent() + " " + event.getSource().getHost();
                Integer count = event.getCount();
                String subObject = event.getInvolvedObject().getFieldPath();
                String lastSeen = TimeUtil.getDate(event.getLastTimestamp());
                String type = event.getType();
                DOSEventPo dosEventPo = DOSEventPo.builder().message(message)
                        .source(source).count(count).subObject(subObject).lastSeen(lastSeen).type(type).build();
                events.add(dosEventPo);
            });
            Collections.reverse(events);
        }
    }

    private Map<String,String> setContainers(PodSpec podSpec, PodStatus podStatus, List<DOSPodContainerPo> containers) {
        Map<String, String> volumeMap = new HashMap<>(16);
        for (int i = 0; i< podSpec.getContainers().size(); i++)  {
            Container container = podSpec.getContainers().get(i);
            String containerName=container.getName();
            String status="-";
            List<ContainerStatus> containerStatuses = podStatus.getContainerStatuses();
            if (containerStatuses != null && !containerStatuses.isEmpty()) {
                ContainerStatus containerStatus = containerStatuses.get(i);
                if (containerStatus != null) {
                    ContainerState containerState = containerStatus.getState();
                    if (containerState != null) {
                        if (containerState.getRunning() != null ) {
                            status = DevOpsConstant.PodConstant.RUNNING;
                        } else if (containerState.getTerminated() != null) {
                            status = containerState.getTerminated().getReason();
                        } else if (containerState.getWaiting() != null) {
                            status = containerState.getWaiting().getReason();
                        } else {
                            status = DevOpsConstant.PodConstant.UNKNOWN;
                        }
                    }
                }
            }

            String image = container.getImage();
            String imagePullPolicy = container.getImagePullPolicy();

            String ports = "--";
            StringBuilder portsSb = new StringBuilder();
            for (ContainerPort port : container.getPorts()) {
                portsSb.append(port.getContainerPort());
                portsSb.append("/");
                portsSb.append(port.getProtocol());
                portsSb.append("  ");
            }
            if (StrUtil.isNotBlank(portsSb.toString().trim())) {
                ports = portsSb.toString().trim();
            }

            String environment = "--";
            StringBuilder environmentSb = new StringBuilder();
            for (EnvVar env : container.getEnv()) {
                environmentSb.append(env.getName());
                environmentSb.append(":");
                environmentSb.append(env.getValue());
                environmentSb.append("  ");
            }
            if (StrUtil.isNotBlank(environmentSb.toString().trim())) {
                environment = environmentSb.toString().trim();
            }

            String[] mounts = null;
            if (container.getVolumeMounts() != null && !container.getVolumeMounts().isEmpty()) {
                List<String> mountsList = new ArrayList<>();
                for (VolumeMount mount : container.getVolumeMounts()) {
                    mountsList.add(mount.getMountPath() + " from " + mount.getName());
                    volumeMap.put(mount.getName(), mount.getMountPath());
                }
                mounts = new String[container.getVolumeMounts().size()];
                mountsList.toArray(mounts);
            }

            String command=String.join(" ",container.getCommand());
            String arguments=String.join(" ",container.getArgs());
            DOSPodContainerPo dosPodContainerPo = DOSPodContainerPo.builder().containerName(containerName).status(status).image(image)
                    .imagePullPolicy(imagePullPolicy) .ports(ports).environment(environment)
                    .mounts(mounts).command(command).arguments(arguments).build();
            containers.add(dosPodContainerPo);
        }
        return volumeMap;
    }

    private void setVolumes(PodSpec podSpec, List<DOSPodVolumePo> volumes, Map<String, String> volumeMap) {
        for (Volume volume : podSpec.getVolumes()) {
            String volumeName = volume.getName();
            String type = "";
            String name = "";
            String path = volumeMap.get(volumeName);

            Optional<ConfigMapVolumeSource> configMapOpt =  Optional.ofNullable(volume.getConfigMap());
            Optional<SecretVolumeSource> secretOpt = Optional.ofNullable(volume.getSecret());
            Optional<PersistentVolumeClaimVolumeSource> pvcOpt = Optional.ofNullable(volume.getPersistentVolumeClaim());
            Optional<ProjectedVolumeSource> projectedOpt = Optional.ofNullable(volume.getProjected());
            Optional<HostPathVolumeSource> hostPathOpt = Optional.ofNullable(volume.getHostPath());
            if (configMapOpt.map(ConfigMapVolumeSource::getName).isPresent()) {
                type = VolumeTypeEnum.CONFIGMAP.getName();
                name = volume.getConfigMap().getName();
            } else if (secretOpt.map(SecretVolumeSource::getSecretName).isPresent()) {
                type = VolumeTypeEnum.SECRET.getName();
                name = volume.getSecret().getSecretName();
            } else if (pvcOpt.map(PersistentVolumeClaimVolumeSource::getClaimName).isPresent()) {
                type = VolumeTypeEnum.PVC.getName();
                name = pvcOpt.map(PersistentVolumeClaimVolumeSource::getClaimName).get();
            } else if (projectedOpt.isPresent()) {
                type = VolumeTypeEnum.PROJECTED.getName();
            } else if (hostPathOpt.map(HostPathVolumeSource::getPath).isPresent()) {
                type = VolumeTypeEnum.HOSTPATH.getName();
            }
            //todo 还有多种类型，用到的时候再加吧
            DOSPodVolumePo dosPodVolumePo = DOSPodVolumePo.builder()
                    .volumeName(volumeName)
                    .kind(type)
                    .name(name)
                    .path(path)
                    .build();
            volumes.add(dosPodVolumePo);
        }
    }

    @Override
    public boolean deleteK8sClusterPod(String podName, int releaseId,int type) {
        Assert.isTrueMvc(releaseId > 0,"releaseId不能为空");
        DOSNamespaceChart namespaceChart;
        if (type == 1) {
            namespaceChart = dosReleaseOutService.getInfoByReleaseId(releaseId);
        }else{
            namespaceChart = dosReleaseOutService.getInfoByResourceId(releaseId);
        }
        Assert.isTrueMvc(null!=namespaceChart,"请检查releaseId是否正确");
        BaseUserVM user = (BaseUserVM) SecurityUtils.getSubject().getPrincipal();
        Assert.isTrueMvc(null!=user,"用户未认证");

        KubernetesClient client = K8sClientUtil.getKubernetesClient(namespaceChart.getConfigPath(),namespaceChart.getConfigContent());
        return !client.pods().inNamespace(namespaceChart.getNamespace()).withName(podName).delete().isEmpty();
    }

    @Override
    public Publisher<Integer> monitorPodStatus(MonitorPodStatusVo podStatusVo) {
        return dealPodStatus(podStatusVo);
    }

    /**
     * @Author heyabo
     * @Description 查询pod的运行状态
     * @Date 14:38 2022/12/2
     * @Param [podStatusVo]
     * @return org.reactivestreams.Publisher<java.lang.String>
     **/
    private Publisher<Integer> dealPodStatus(MonitorPodStatusVo podStatusVo) {

        return Flux.create(emitter -> {
            //入参校验
            List<DOSPodVo> paramPods = podStatusVo.getPods();
            List<DOSPodVo> webPods = new ArrayList<>();
            Assert.isTrueMvc(CollectionUtil.isNotEmpty(paramPods),"pods参数不能为空");
            Integer releaseId = null;
            Integer releaseType = null;
            for (DOSPodVo podVo : paramPods) {
                if (podVo.getId() != null && podVo.getId() > 0 && releaseId == null) {
                    releaseId = podVo.getId();
                }
                if (podVo.getType() != null && podVo.getType() > 0 && releaseType == null) {
                    releaseType = podVo.getType();
                }
                String podName = podVo.getPodName();
                String podStatus = podVo.getPodStatus();
                if (StrUtil.isNotBlank(podName) && StrUtil.isNotBlank(podStatus)) {
                    webPods.add(DOSPodVo.builder().podName(podName).podStatus(podStatus).build());
                }
            }

            Assert.isTrueMvc(releaseId != null && releaseId > 0,"releaseId数据异常");
            Assert.isTrueMvc(releaseType != null && releaseType > 0,"releaseType数据异常");

            DOSNamespaceChart namespaceChart = null;
            if (releaseType == 1) {
                namespaceChart = dosReleaseOutService.getInfoByReleaseId(releaseId);
            } else if (releaseType == 2) {
                namespaceChart = dosReleaseOutService.getInfoByResourceId(releaseId);
            }
            Assert.isTrueMvc(namespaceChart != null,"releaseId数据异常");
            namespaceChart.setReleaseType(releaseType);
            namespaceChart.setReleaseId(releaseId);

            boolean skipFlag = false;
            int recycleCount = 0;
            while (!skipFlag && recycleCount < 120) {
                recycleCount ++ ;
                ThreadUtil.sleep(5000L);

                // 查询pod
                PodList podList = dealPodList(namespaceChart);
                List<DOSPodVo> k8sPods = new ArrayList<>();
                if (podList != null && CollectionUtil.isNotEmpty(podList.getItems())) {
                    List<Pod> items = podList.getItems();
                    for (Pod pod : items) {
                        String podName = pod.getMetadata().getName();
                        String podStatus = DevOpsConstant.PodConstant.UNKNOWN;
                        List<ContainerStatus> containerStatus = pod.getStatus().getContainerStatuses();
                        if (containerStatus.size() > 0) {
                            ContainerStatus firstContainerStatus = containerStatus.get(0);
                            ContainerState containerState = firstContainerStatus.getState();
                            if (containerState.getRunning() != null) {
                                podStatus = DevOpsConstant.PodConstant.RUNNING;
                            } else if (containerState.getTerminated() != null) {
                                podStatus = containerState.getTerminated().getReason();
                            } else if (containerState.getWaiting() != null) {
                                podStatus = containerState.getWaiting().getReason();
                            }
                        }
                        k8sPods.add(DOSPodVo.builder().podName(podName).podStatus(podStatus).build());
                    }
                }
                boolean sendFlag = false;
                if (webPods.size() != k8sPods.size()) {
                    //pod数量都不一致了
                    log.debug("********************pod数量发生变化了，页面pod数量=" + webPods.size()
                                    + "  k8s集群pod数量=" + k8sPods.size());

                    sendFlag = true;
                } else {
                    if (k8sPods.size() > 0) {
                        Map<String, List<DOSPodVo>> k8sPodsMap = k8sPods.stream().collect(Collectors.groupingBy(DOSPodVo::getPodName));

                        for (DOSPodVo webPod : webPods) {
                            String webPodName = webPod.getPodName();
                            String webPodStatus = webPod.getPodStatus();
                            if (k8sPodsMap.get(webPodName) != null) {
                                //web页面的pod还在 比较status
                                DOSPodVo k8sPod = k8sPodsMap.get(webPodName).get(0);
                                if (!webPodStatus.equalsIgnoreCase(k8sPod.getPodStatus())) {
                                    //状态发生变化了
                                    log.debug("********************pod状态发生变化，podName=" + webPodName
                                            + " 新状态=" + k8sPod.getPodStatus()
                                            + " 原状态=" + webPodStatus);
                                    sendFlag = true;
                                    break;
                                }
                            } else {
                                //web页面的pod不存在了
                                log.debug("********************pod被删除了，podName="+webPodName);
                                sendFlag = true;
                                break;
                            }
                        }

                    }
                }
                if (sendFlag) {
                    emitter.next(1);
                    emitter.complete();
                    skipFlag = true;
                }
            }

        });
    }

    /**
     * @Author heyabo
     * @Description
     * @Date 09:54 2022/6/1
     * @Param [message]
     * @return java.lang.String[]
     **/
    private String[] getSplitStr(Map<String,String> map){
        String[] messages = null;
        if (CollectionUtil.isNotEmpty(map)) {
            messages = map.toString()
                    .replace("{","").replace("}","")
                    .split(",");
        }
        return messages;
    }

    /**
     * @Author heyabo
     * @Description 解析yaml文件
     * @Date 16:10 2022/8/10
     * @Param [yaml]
     * @return java.util.Map<java.lang.String,java.lang.Object>
     **/
    private Map<String,Object> getMatchLabels(String yaml) {
        return (Map<String, Object>) YamlUtils.getValues("spec.selector.matchLabels",yaml);
    }
}
